﻿//#define reevaluate_rules

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using agree.schema;
using agree.mrs;

using alib;
using alib.Array;
using alib.BitArray;
using alib.Collections;
using alib.Collections.ReadOnly;
using alib.Combinatorics;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

namespace agree.transfer
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TransferExecutor : m_scalar_base<Mrs, IIdentList<Hypothesis>>, ρρTransferRules
	{
		public TransferExecutor(IRtParent parent, Mrs input_mrs)
			: base(parent, input_mrs)
		{
			this.TransferRules = TransferRules.None;
		}
		public TransferExecutor(IRtParent parent)
			: this(parent, default(Mrs))
		{
		}

		public bool f_stop;

		TransferQueue xfer_q;

		public long next_hyp_id;

		IdentList<Hypothesis> terminals;

		public TransferRules TransferRules { get; set; }

		Mrs input_mrs { get { return t0; } }

		void init()
		{
			terminals = new IdentList<Hypothesis>(this);

			xfer_q = new TransferQueue(this, do_hyp);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary> enqueue first item </summary>
		protected override IIdentList<Hypothesis> start(Mrs input_mrs)
		{
			init();

			dbg_writeline(" input ".PadCenter(60, '-'));
			input_mrs.RELS.show_rels();

			dbg_writeline(new String('-', 60));

			var tr = new TransferRules(this);
			tr.load();
			this.TransferRules = tr;

			dbg_writeline(" rules ".PadCenter(60, '-'));
			foreach (var rule in TransferRules)
			{
				dbg_writeline((" " + rule.Name + " ").PadCenter(60, '-'));
				rule.L1.a.Append(arrow_rel.Instance).Concat(rule.L2.a).show_rels();
			}

			dbg_writeline(new String('-', 60));

			Hypothesis hyp = new Hypothesis(this, Collection<Relation>.None);
			xfer_q.Enqueue(hyp);

			dbg_writeline("------------------------");
			dbg_writeline("{0} terminals", terminals.Count);
			for (int i = 0; i < terminals.Count; i++)
			{
				dbg_write("--- #{0} ", i);
				terminals[i].show_hyp();
				dbg_writeline();
			}

			return terminals;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary> start processing a hypothesis </summary>
		void do_hyp(Hypothesis old_hyp)
		{
			uint h2, h1 = (uint)old_hyp.ra.GetHashCode();

			//bool f_any = false;
			foreach (var rule in TransferRules)
			{
#if reevaluate_rules
				/// for now, generally don't re-evaluate rules
				if (old_hyp.Contains(rule))
					continue;
#endif
				Hypothesis new_hyp;
				Dictionary<Variable, _inp_refs> rul_map;

				foreach (var inp_rel_subset in input_mrs.RELS.Variations(rule.L1.Count))
				{
					if (f_stop)
						Debugger.Break();

					RuleApplication ra;
					ra.rule = rule;
					ra.rg_inp_rel_ix = inp_rel_subset.Select(ir => ir.ix).ToArray();


					if ((h2 = (uint)ra.GetHashCode()) == h1)
					{
						if (!ra.Equals(old_hyp.ra))
							throw new Exception();
						continue;
					}
					else if (h2 < h1)
					{
						/// since rule applications are order-independent, this rule sequence will be done later
						continue;
					}

					//if (old_hyp.Contains(ra))
					//	continue;

					var match = inp_rel_subset.Zip(rule.L1, (ir, rr) => new InpRulRelPair { inp = ir, rul = rr }).ToArray();

					if ((rul_map = get_rule_var_map(rule, match)) == null)
						continue;

					if ((new_hyp = build_new_hyp(old_hyp, ra, rul_map)) != null)
					{
						//dbg_writeline(new_hyp.Report());
						new_hyp.show_hyp();
						xfer_q.Enqueue(new_hyp);
						//f_any = true;
					}
				}
			}

			/// this hypothesis did not spawn any descendants.
			//if (!f_any)
			{
				if (old_hyp.LOCUS.Count > 0 && old_hyp.LOCUS.All(loc => WellFormednessCondition(loc)))
					terminals.Add(old_hyp);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		public bool WellFormednessCondition(Relation rel)
		{
			var b = rel.LBL != null && rel.PRED != null;
			if (!b)
				Nop.X();
			return b;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// try to obtain mapping from rule source vars to input vars according to specified permutation. 
		/// Return null if matchup is not acceptable. This can happen if the rule does not subsume the input
		/// (via typing or coreferencing). If the source side is ok, continue by adding mappings for variables on 
		/// the target side of the rule (which were not seen on the source side).
		Dictionary<Variable, _inp_refs> get_rule_var_map(TransferRule r, InpRulRelPair[] matchup)
		{
			/// the map from rule-to-input variables can actually be Var-to-Var but for preliminary
			/// testing, the value side is a list of VarPtrs. It would be an error if they do not all
			/// point to the same variable.
			_inp_refs lvp;
			Dictionary<Variable, _inp_refs> rvm = new Dictionary<Variable, _inp_refs>();

			dbg_writeline("\n============ check {0} rel pairs for '{1}' ============", matchup.Length, r.Name);
			for (int i = 0; i < matchup.Length; i++)
			{
				InpRulRelPair irp = matchup[i];

				dbg_writeline("{0,-17} {1}", irp.rul.ix, irp.inp.ix);

				for (int j = 0; true /*j < rel_base.rgfeat.Length*/; j++)
				{
					throw not.impl;
					var np_inp = new NodePtr(irp.inp, ""/*j*/);
					var np_rul = new NodePtr(irp.rul, ""/*j*/);

					dbg_writeline("{0,-17} {1}", np_rul.ShortInf, np_inp.ShortInf);

					if (np_rul.var == null)		/// rule can be *top* here...
						continue;
					else if (np_inp.var == null)	/// ...but rule cannot be more specific than the input
					{
						dbg_writeline(" >>> reject (not subsuming) [{0}] [input null]", np_rul.var);
						return null;
					}

					/// (same as above)
					if (!np_rul.Subsumes(np_inp))
					{
						//dbg_writeline(" >>> reject (incompatible types) [{0}] [{1}]", np_rul.Var.Type, np_inp.Var.Type);
						return null;
					}

					/// check coreferencing subsumption
					if (!rvm.TryGetValue(np_rul.var, out lvp))
						rvm.Add(np_rul.var, lvp = new _inp_refs());
					else if (np_inp.var != lvp[0].var)
					{
						dbg_writeline(" >>> reject (corefs not subsuming) [{0}] [{1}]", np_rul.var, np_inp.var);
						return null;
					}
					if (!lvp.Contains(np_inp))
						lvp.Add(np_inp);
				}
			}

			dbg_writeline("\n >>> ok!", r.Name);
			return rvm;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		Hypothesis build_new_hyp(Hypothesis h_old, RuleApplication ra, Dictionary<Variable, _inp_refs> rul_map)
		{
			/// make a copy of the current state of target loci
			Hypothesis h_new = new Hypothesis(this, h_old, ra);

			foreach (var rule_tgt_rel in ra.rule.L2)
				if (!deploy_tgt_locus(rule_tgt_rel, rul_map, h_new))
					return null;

			return h_new;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		bool deploy_tgt_locus(Relation rule_tgt_rel, Dictionary<Variable, _inp_refs> rul_map, Hypothesis h_new)
		{
			Relation loc = null;
			_inp_refs rgv = null;
			foreach (var rule_node in rule_tgt_rel)
			{
				/// input variable that will be set into a target locus.
				Variable v_inp;

				/// map rule variable to input
				bool f_create_var = !rul_map.TryGetValue(rule_node.var, out rgv);
				if (f_create_var)
				{
					/// if it's not in the map then it is a rule target-side-only variable.
					/// A new locus will be created below, or the current one will be tried.
					/// The new variable will be added to the map also, using the _target_ locus.
					v_inp = h_new.CreateInstance(rule_node.var.GetType());
				}
				else if (rgv[0].rel is Relation)
				{
					/// recover a target-side-only variable from a previous loop iteration
					if (loc == null)
						loc = (Relation)rgv[0].rel;
					v_inp = rgv[0].var;
				}
				else
				{
					if (rgv.CountDistinct(_np => _np.var) != 1)
						throw new Exception();

					if (rgv.Count > 1)
						throw new Exception();

					var inp_rel = (Relation)rgv[0].rel;

					/// conflate existing hypothesis loci based on rule target loci
					/// see if there's already a locus in the new hypothesis that is bound to the same
					/// input rel as the variable we are now considering.
					//if (rule_node.arg_ix == 0)
					//	Nop.X();

					//hyp_tgt_rel = h_new.tgt_rels.FirstOrDefault(tgt_rel => tgt_rel.Any(vp =>
					//	{
					//		var b1 = inp_rel.Vars.Contains(vp.v);
					//		//var b2 = inp_rel[vp].v == vp.v;
					//		//if (b1 != b2)
					//		//	Nop.X();
					//		return b1;
					//	}));

					//var hyp_tgt_rel_old = h_new.tgt_rels.FirstOrDefault(r => r.Vars.Any(rv => inp_rel.Vars.Contains(rv)));

					h_new.LOCUS.a.Prepend(arrow_rel.Instance).Prepend(rule_tgt_rel).show_rels();


					// um, do we want the first hypothesis rel that is not ruled out by the input rel, if any?

					// no that's not right either. 

					//if (hyp_tgt_rel != hyp_tgt_rel_old)
					//	Nop.X();

					//if (h_old.tgt_rels.Count > 0)
					//	Nop.X();


					var zx = h_new.LOCUS.Where(rel =>
					{
						var bbb = rel.All(np =>
						{
							if (np.var == null)
								throw new Exception();
							var qq = rel;
							var z = rul_map.Count;
							var rnv = (Variable)rule_tgt_rel[np.name];
							if (rnv == null)
								return true;

							//rel_base.NodePtr eir;
							_inp_refs irx;
							Variable fff;
							if (rul_map.TryGetValue(rnv, out irx))
							{
								fff = irx[0].var;
								Nop.X();
							}
							else
							{
								fff = null;
								Nop.X();
							}

							if (fff.Name == np.var.Name)
								return true;
							return false;
						});

						return bbb;
					}).ToArray();

					if (zx.Length == 0)
					{
						//hyp_tgt_rel = null;
						Nop.X();
					}
					else
					{
						var hyp_tgt_rel = zx[0];

						if (zx.Length > 1)
							Nop.X();

						if (h_new.LOCUS.Count == 0)
						{
							/// no target loci to worry about
							Nop.X();
						}
						else if (loc == null)
						{
							/// we pulled up a locus, but don't have one currently so we can just use it.
							loc = hyp_tgt_rel;
						}
						else if (loc == hyp_tgt_rel)
						{
							/// called up the same locus into which we placed the previous variable.
							Nop.X();
						}
#if true
						else
						{
							/// rule is now trying to conflate two loci in the target.
							foreach (var np_ext in hyp_tgt_rel)
							{
								throw not.impl;
#if false
								loc[np_ext.feat] = np_ext.var;
								if (!loc[np_ext.feat].try_set_type(np_ext.var))
								{
									dbg_writeline(" >>> reject (target rel merging) [{0}] [{1}]", rule_node.var, np_ext.var);
									return false;
								}
#endif
							}

							h_new.LOCUS.Remove(hyp_tgt_rel);
							hyp_tgt_rel = null;
						}
#endif
					}

					v_inp = rgv[0].var;
				}

				if (loc == null)
				{
					/// if we still don't have a locus, always create a new one. We can conflate them later
					/// if need be.
					throw not.impl;
					//h_new.LOCUS.Add(loc = new locus());
				}

				/// add the variable to the target-side locus.
				throw not.impl;
				//if (!loc[rule_node.Arg].try_set_type(v_inp))
				loc[rule_node.name] = v_inp;
				{
					dbg_writeline(" >>> reject (target rel collision 1) [{0}] [{1}]", rule_node.var, v_inp);
					return false;
				}

				/// add target-side vars from rule. for now let's make copies of these rule vars so they don't 
				/// unintentionally conflate in a later application of this rule.
				/// if we copied a variable because it only appeared on the target side, add it to the
				/// dictionary so we will pick it up on later iterations.
				if (f_create_var)
					rul_map.Add(rule_node.var, new _inp_refs() { new NodePtr(loc, rule_node.name) });
			}
			return true;
		}


		[DebuggerStepThrough]
		public static void dbg_write(String fmt, params Object[] args) { xfer_ext.dbg_write(fmt, args); }
		[DebuggerStepThrough]
		public static void dbg_writeline(String fmt, params Object[] args) { xfer_ext.dbg_writeline(fmt, args); }
		[DebuggerStepThrough]
		public static void dbg_writeline() { xfer_ext.dbg_writeline(); }
	};
}

